#include "FileWatcher.h"
#include <functional>
#include <algorithm>
#define chCloseHandle(handle) if(handle) {CloseHandle(handle); handle=NULL;}


CFileWatcher::CFileWatcher()
	:m_IOCP(NULL)
	, m_stopEvent(FALSE, TRUE)
	, m_HDir(INVALID_HANDLE_VALUE)
	, m_pfwi(NULL)
{
}


CFileWatcher::~CFileWatcher()
{
}

int CFileWatcher::StartListener(std::string strPath, std::string strFilter, BOOL bWatchSub, DWORD dwAttr)
{
	m_strPath = strPath;
	m_bWatchSub = bWatchSub;
	m_strFilter = strFilter;
	m_dwAttr = dwAttr;
	StopListener();

	m_IOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0);
	if (m_IOCP == NULL)
	{
		return -1;
	}
	
	if (!OpenDirectory())
	{
		chCloseHandle(m_IOCP);
		return -2;
	}
	m_pfwi = std::make_shared<FileWatchInfo>();

	m_IOCP = ::CreateIoCompletionPort(m_HDir, m_IOCP, (ULONG_PTR)m_pfwi.get(), 0);
	if (m_IOCP == INVALID_HANDLE_VALUE)
	{
		chCloseHandle(m_HDir);
		return -3;
	}

	m_hMonitor = std::make_shared<std::thread>(&CFileWatcher::doMonitorThread, this);
	m_hProcess = std::make_shared<std::thread>(&CFileWatcher::doProcessThread, this);

	m_pfwi->StartMonitor(m_IOCP);
	return 0;
}

void CFileWatcher::StopListener()
{
	DWORD dw = GetCurrentThreadId();
	m_stopEvent.SetEvent();

	if (m_pfwi) m_pfwi->StopMonitor(m_IOCP);
	if (m_hMonitor && m_hMonitor->joinable())
	{
		m_hMonitor->join();
	}

	if (m_hProcess && m_hProcess->joinable())
	{
		m_hProcess->join();
	}
	m_queueNotify.Clear();

	if (m_pfwi) m_pfwi.reset();
	chCloseHandle(m_HDir);
	chCloseHandle(m_IOCP);
	m_stopEvent.ResetEvent();

}


bool CFileWatcher::OpenDirectory()
{
	m_HDir = CreateFileA(m_strPath.c_str()
		, FILE_LIST_DIRECTORY
		, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
		, NULL															// security descriptor
		, OPEN_EXISTING												// how to create
		, (FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED)		// file attributes
		, NULL															// file with attributes to copy
		);
	return (m_HDir != INVALID_HANDLE_VALUE);
}

void CFileWatcher::doMonitorThread()
{
	DWORD dwtransferred(0);
	LPOVERLAPPED pOverlapped = NULL;
	FileWatchInfo *pfwi = NULL;

	while (!m_stopEvent.Wait(10))
	{
		if (GetQueuedCompletionStatus(m_IOCP, &dwtransferred, (PULONG_PTR)&pfwi, &pOverlapped, 3000))
		{
			if (pfwi == NULL)
			{
				break;
			}


			if (dwtransferred == 0)  //
			{
				//ASSERT(0);
 				continue;
			}

			switch (pfwi->runState)
			{
			case RUNNING_STATE_START_MONITORING:
				if (::ReadDirectoryChangesW(m_HDir
					, pfwi->buffer
					, READ_DIR_CHANGE_BUFFER_SIZE
					, m_bWatchSub
					, m_dwAttr
					, &pfwi->dwSize
					, &pfwi->overlapped
					, NULL
					))
				{
					pfwi->runState = RUNNING_STATE_NORMAL;
				}
				break;
			case RUNNING_STATE_NORMAL:
				{
					int len = dwtransferred;
					if (len > 0)
					{
						std::vector<char> bufArr;
						bufArr.resize(len);
						memcpy(&bufArr[0], m_pfwi->buffer, len);

						m_queueNotify.PushBack(std::forward<std::vector<char>>(bufArr));
					}

				}
				
				if (!::ReadDirectoryChangesW(m_HDir
					, pfwi->buffer
					, READ_DIR_CHANGE_BUFFER_SIZE
					, m_bWatchSub
					, m_dwAttr
					, &pfwi->dwSize
					, &pfwi->overlapped
					, NULL
					))
				{
					break;
				}

				break;

			case RUNNING_STATE_STOP:
				break;

			}
		}
	}

}

void CFileWatcher::doProcessThread()
{
	while (!m_stopEvent.Wait(10))
	{
		std::vector<char> vetbuf;
		if (m_queueNotify.Pop(&vetbuf, 1000))
		{
			ProcessChangeNotifications(&vetbuf[0]);
		}
	}

}


void CFileWatcher::ProcessChangeNotifications(char* pBase)
{
	if (!pBase) return;

	while (1)
	{
		PFILE_NOTIFY_INFORMATION fni = (FILE_NOTIFY_INFORMATION*)pBase;
		std::string strFileName = getFileName(fni);
		strFileName = m_strPath + strFileName;

		std::string strNewName;
		if (fni->Action == FILE_ACTION_RENAMED_OLD_NAME)
		{
			if (!fni->NextEntryOffset)
				break;
			pBase += fni->NextEntryOffset;
			fni = (FILE_NOTIFY_INFORMATION*)pBase;
			strNewName = getFileName(fni);
			strNewName = m_strPath + strNewName;

		}

		switch (fni->Action)
		{
		case FILE_ACTION_ADDED:
			OnAdd(strFileName);
			break;
		case FILE_ACTION_REMOVED:
			OnRemove(strFileName);
			break;
		case FILE_ACTION_MODIFIED:
			OnModify(strFileName);
			break;
		case FILE_ACTION_RENAMED_NEW_NAME:
			OnRename(strFileName, strNewName);
			break;
		default:
			break;
		}


		if (!fni->NextEntryOffset)
			break;
		pBase += fni->NextEntryOffset;
	}
}

std::string CFileWatcher::getFileName(PFILE_NOTIFY_INFORMATION fni)
{
	char	temp[MAX_PATH] = { 0 };
	WideCharToMultiByte(CP_ACP, 0, fni->FileName, fni->FileNameLength / 2, temp, MAX_PATH, NULL, NULL);
	std::string	fileName = temp;
	return fileName;
}
